Features
Install
yarn add use-debounce
npm i use-debounce --save
Demos
The simplest way to start playing around with use-debounce is with this CodeSandbox snippet:
https://codesandbox.io/s/kx75xzyrq7
More complex example with searching for matching countries using debounced input: https://codesandbox.io/s/rr40wnropq (thanks to https://twitter.com/ZephDavies)
Changelog
https://github.com/xnimorz/use-debounce/blob/master/CHANGELOG.md
Simple values debouncing
According to https://twitter.com/dan_abramov/status/1060729512227467264
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
This hook compares prev and next value using shallow equal. It means, setting an object {}
will trigger debounce timer. If you have to compare objects (https://github.com/xnimorz/use-debounce/issues/27#issuecomment-496828063), you can use useDebouncedCallback
, that is explained below:
Debounced callbacks
Besides useDebounce
for values you can debounce callbacks, that is the more commonly understood kind of debouncing.
Example with Input (and react callbacks): https://codesandbox.io/s/x0jvqrwyq
import { useDebouncedCallback } from 'use-debounce';
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
const debounced = useDebouncedCallback(
(value) => {
setValue(value);
},
1000
);
return (
<div>
<input defaultValue={defaultValue} onChange={(e) => debounced.callback(e.target.value)} />
<p>Debounced value: {value}</p>
</div>
);
}
Example with Scroll (and native event listeners): https://codesandbox.io/s/32yqlyo815
function ScrolledComponent() {
const updatedCount = useRef(0);
updatedCount.current++;
const [position, setPosition] = useState(window.pageYOffset);
const debounced = useDebouncedCallback(
() => {
setPosition(window.pageYOffset);
},
800
);
useEffect(() => {
const unsubscribe = subscribe(window, 'scroll', debounced.callback);
return () => {
unsubscribe();
};
}, []);
return (
<div style={{ height: 10000 }}>
<div style={{ position: 'fixed', top: 0, left: 0 }}>
<p>Debounced top position: {position}</p>
<p>Component rerendered {updatedCount.current} times</p>
</div>
</div>
);
}
Returned value from debounced.callback
Subsequent calls to the debounced function debounced.callback
return the result of the last func invocation.
Note, that if there are no previous invocations it's mean you will get undefined. You should check it in your code properly.
Example:
it('Subsequent calls to the debounced function `debounced.callback` return the result of the last func invocation.', () => {
const callback = jest.fn(() => 42);
let callbackCache;
function Component() {
const debounced = useDebouncedCallback(callback, 1000);
callbackCache = debounced.callback;
return null;
}
Enzyme.mount(<Component />);
const result = callbackCache();
expect(callback.mock.calls.length).toBe(0);
expect(result).toBeUndefined();
act(() => {
jest.runAllTimers();
});
expect(callback.mock.calls.length).toBe(1);
const subsequentResult = callbackCache();
expect(callback.mock.calls.length).toBe(1);
expect(subsequentResult).toBe(42);
});
Advanced usage
Cancel, maxWait and memoization
- Both
useDebounce
and useDebouncedCallback
works with maxWait
option. This params describes the maximum time func is allowed to be delayed before it's invoked. - You can cancel debounce cycle, by calling
cancel
callback
The full example you can see here https://codesandbox.io/s/4wvmp1xlw4
import React, { useState, useCallback } from 'react';
import ReactDOM from 'react-dom';
import { useDebouncedCallback } from 'use-debounce';
function Input({ defaultValue }) {
const [value, setValue] = useState(defaultValue);
const debounced = useDebouncedCallback(
(value) => {
setValue(value);
},
500,
{ maxWait: 2000 }
);
return (
<div>
<input defaultValue={defaultValue} onChange={(e) => debounced.callback(e.target.value)} />
<p>Debounced value: {value}</p>
<button onClick={debounced.cancel}>Cancel Debounce cycle</button>
</div>
);
}
const rootElement = document.getElementById('root');
ReactDOM.render(<Input defaultValue="Hello world" />, rootElement);
Flush method
useDebouncedCallback
has flush
method. It allows to call the callback manually if it hasn't fired yet. This method is handy to use when the user takes an action that would cause the component to unmount, but you need to execute the callback.
import React, { useState, useCallback } from 'react';
import { useDebouncedCallback } from 'use-debounce';
function InputWhichFetchesSomeData({ defaultValue, asyncFetchData }) {
const debounced = useDebouncedCallback(
(value) => {
asyncFetchData;
},
500,
{ maxWait: 2000 }
);
useEffect(
() => () => {
debounced.flush();
},
[debounced]
);
return <input defaultValue={defaultValue} onChange={(e) => debounced.callback(e.target.value)} />;
}
Pending method
pending
method shows whether component has pending callbacks. Works for both useDebounce
and useDebouncedCallback
:
function Component({ text }) {
const debounced = useDebouncedCallback(useCallback(() => {}, []), 500);
expect(debounced.pending()).toBeFalsy();
debounced.callback();
expect(debounced.pending()).toBeTruthy();
debounced.flush();
expect(debounced.pending()).toBeFalsy();
return <span>{text}</span>;
}
leading/trailing calls
Both useDebounce
and useDebouncedCallback
work with the leading
and trailing
options. leading
param will execute the function once immediately when called. Subsequent calls will be debounced until the timeout expires. trailing
option controls whenever to call the callback after timeout again.
For more information on how leading debounce calls work see: https://lodash.com/docs/#debounce
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000, { leading: true });
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
Options:
You can provide additional options as a third argument to both useDebounce
and useDebouncedCallback
:
useThrottledCallback
You are able to use throttled callback with this library also (starting 5.2.0 version).
For this purpose use:
import useThrottledCallback from 'use-debounce/useThrottledCallback';
or
import { useThrottledCallback } from 'use-debounce';
Several examples:
-
Avoid excessively updating the position while scrolling.
const scrollHandler = useThrottledCallback(updatePosition, 100);
window.addEventListener('scroll', scrollHandler);
-
Invoke renewToken
when the click event is fired, but not more than once every 5 minutes.
const throttled = useThrottledCallback(renewToken, 300000, { 'trailing': false })
<button onClick={throttled}>click</button>
All the params for useThrottledCallback
are the same as for useDebouncedCallback
except maxWait
option. As it's not needed for throttle callbacks.
Special thanks:
@tryggvigy — for managing lots of new features of the library like trailing and leading params, throttle callback, etc;
@omgovich — for reducing bundle size.